#!/bin/bash
# **********************************************************
# * Author : liangliangSu
# * Email : sll917@hotmail.com
# * Create time : 2022-11-12 15:59
# * Filename : 01正则表达式.sh
# **********************************************************
:<<!
由於一般我们在练习正规表示法时，使用的是兼容於 POSIX 的标准，因此就使用『 C 』这个语系(注1)！ 因此，底下的很多练习都是使用『 LANG=C 』这个语系数据来进行的喔！ 另外，为了要避免这样编码所造成的英文与数字的撷取问题，因此有些特殊的符号我们得要了解一下的！ 这些符号主要有底下这些意义：(注1)
一，特殊符号 代表意义
[:alnum:] 代表英文大小写字符及数字，亦即 0-9, A-Z, a-z
[:alpha:] 代表任何英文大小写字符，亦即 A-Z, a-z
[:blank:] 代表空格键与 [Tab] 按键两者
[:cntrl:] 代表键盘上面的控制按键，亦即包括 CR, LF, Tab, Del.. 等等
[:digit:] 代表数字而已，亦即 0-9
[:graph:] 除了空格符 (空格键与 [Tab] 按键) 外的其他所有按键
[:lower:] 代表小写字符，亦即 a-z
[:print:] 代表任何可以被打印出来的字符
[:punct:] 代表标点符号 (punctuation symbol)，亦即："  ? ! ; : # $...
[:upper:] 代表大写字符，亦即 A-Z
[:space:] 任何会产生空白的字符，包括空格键, [Tab], CR 等等
[:xdigit:] 代表 16 进位的数字类型，因此包括： 0-9, A-F, a-f 的数字与字符

二，基础正规表示法字符汇整(characters)
基础的正规表示法特殊字符汇整如下：
RE 字符		意义与范例
^word
意义：待搜寻的字符串(word)在行首！
范例：搜寻行首为 # 开始的那一行，并列出行号
grep -n '^#' regular_express.txt

word$
意义：待搜寻的字符串(word)在行尾！
范例：将行尾为 ! 的那一行打印出来，并列出行号
grep -n '!$' regular_express.txt

.
意义：代表『一定有一个任意字符』的字符！
范例：搜寻的字符串可以是 (eve) (eae) (eee) (e e)， 但不能仅有 (ee) ！亦即 e 与 e
中间『一定』仅有一个字符，而空格符也是字符！
grep -n 'e.e' regular_express.txt

\
意义：跳脱字符，将特殊符号的特殊意义去除！\. \* \$ \^ \[ \\ \' 转意字符除以上7种之外不因该出现其他字符
范例：搜寻含有单引号 ' 的那一行！
grep -n \' regular_express.txt

*
意义：重复零个到无穷多个的前一个 RE 字符
范例：找出含有 (es) (ess) (esss) 等等的字符串，注意，因为 * 可以是 0 个，所以 es 也
是符合带搜寻字符串。另外，因为 * 为重复『前一个 RE 字符』的符号， 因此，在 * 之前必须
要紧接着一个 RE 字符喔！例如任意字符则为 『.*』 ！
grep -n 'ess*' regular_express.txt

[list]
意义：字符集合的 RE 字符，里面列出想要撷取的字符！
范例：搜寻含有 (gl) 或 (gd) 的那一行，需要特别留意的是，在 [] 当中『谨代表一个待搜寻
的字符』， 例如『 a[afl]y 』代表搜寻的字符串可以是 aay, afy, aly 即 [afl] 代表 a 或
f 或 l 的意思！
grep -n 'g[ld]' regular_express.txt

[n1-n2]
意义：字符集合的 RE 字符，里面列出想要撷取的字符范围！
范例：搜寻含有任意数字的那一行！需特别留意，在字符集合 [] 中的减号 - 是有特殊意义的，
他代表两个字符之间的所有连续字符！但这个连续与否与 ASCII 编码有关，因此，你的编码需要
设定正确(在 bash 当中，需要确定 LANG 与 LANGUAGE 的变量是否正确！) 例如所有大写字
符则为 [A-Z]
grep -n '[A-Z]' regular_express.txt

[^list]
意义：字符集合的 RE 字符，里面列出不要的字符串或范围！
范例：搜寻的字符串可以是 (oog) (ood) 但不能是 (oot) ，那个 ^ 在 [] 内时，代表的意
义是『反向选择』的意思。 例如，我不要大写字符，则为 [^A-Z]。但是，需要特别注意的是，
如果以 grep -n [^A-Z] regular_express.txt 来搜寻，却发现该文件内的所有行都被列
出，为什么？因为这个 [^A-Z] 是『非大写字符』的意思， 因为每一行均有非大写字符，例如第
一行的 "Open Source" 就有 p,e,n,o.... 等等的小写字
grep -n 'oo[^t]' regular_express.txt

\{n,m\}
意义：连续 n 到 m 个的『前一个 RE 字符』
意义：若为 \{n\} 则是连续 n 个的前一个 RE 字符，
意义：若是 \{n,\} 则是连续 n 个以上的前一个 RE 字符！ 范例：在 g 与 g 之间有 2 个到
3 个的 o 存在的字符串，亦即 (goog)(gooog)
grep -n 'go\{2,3\}g' regular_express.txt

再次强调：『正规表示法的特殊字符』与一般在指令列输入指令的『通配符』并不相同， 例如，在
通配符当中的* 代表的是『0 ~ 无限多个字符』的意思，但是在正规表示法当中， * 则是『重复0
到无穷多个的前一个RE 字符』的意思～使用的意义并不相同，不要搞混了！
举例来说，不支持正规表示法的ls 这个工具中，若我们使用『ls -l * 』代表的是任意档名的文件，
而『ls -l a* 』代表的是以a 为开头的任何档名的文件， 但在正规表示法中，我们要找到含有以 a
为开头的文件，则必须要这样：(需搭配支持正规表示法的工具)

三，延伸正规表示法
事实上，一般读者只要了解基础型的正规表示法大概就已经相当足够了，不过，某些时刻为了要简化整个命令操作， 了解一下使用范围更广的延伸型正规表示法的表示式会更方便呢！举个简单的例子好了，在上节的例题三的最后一个例子中，我们要去除空白行与行首为 # 的行列，使用的是

grep -v '^$' regular_express.txt | grep -v '^#'
需要使用到管线命令来搜寻两次！那么如果使用延伸型的正规表示法，我们可以简化为：

egrep -v '^$|^#' regular_express.txt
延伸型正规表示法可以透过群组功能『 | 』来进行一次搜寻！那个在单引号内的管线意义为『或 or』啦！ 是否变的更简单呢？此外，grep 默认仅支持基础正规表示法，如果要使用延伸型正规表示法，你可以使用 grep -E ， 不过更建议直接使用 egrep ！直接区分命令比较好记忆！其实 egrep 与 grep -E 是类似命令别名的关系啦！

RE 字符		意义与范例
+
意义：重复『一个或一个以上』的前一个 RE 字符
范例：搜寻 (god) (good) (goood)... 等等的字符串。 那个 o+ 代表『一个以上的 o 』所以，底下
的执行成果会将第 1, 9, 13 行列出来。
egrep -n 'go+d' regular_express.txt

?
意义：『零个或一个』的前一个 RE 字符
范例：搜寻 (gd) (god) 这两个字符串。 那个 o? 代表『空的或 1 个 o 』所以，上面的执行成果会将
第 13, 14 行列出来。 有没有发现到，这两个案例( 'go+d' 与 'go?d' )的结果集合与 'go*d' 相同？
想想看，这是为什么喔！ ^_^
egrep -n 'go?d' regular_express.txt

|
意义：用或( or )的方式找出数个字符串
范例：搜寻 gd 或 good 这两个字符串，注意，是『或』！ 所以，第 1,9,14 这三行都可以被打印出来喔！
那如果还想要找出 dog 呢？
egrep -n 'gd|good' regular_express.txt
egrep -n 'gd|good|dog' regular_express.txt

()
意义：找出『群组』字符串
范例：搜寻 (glad) 或 (good) 这两个字符串，因为 g 与 d 是重复的，所以， 我就可以将 la 与 oo 列
于 ( ) 当中，并以 | 来分隔开来，就可以啦！
egrep -n 'g(la|oo)d' regular_express.txt

()+
意义：多个重复群组的判别
范例：将『AxyzxyzxyzxyzC』用 echo 叫出，然后再使用如下的方法搜寻一下！
echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'
上面的例子意思是说，我要找开头是 A 结尾是 C ，中间有一个以上的 "xyz" 字符串的意思～
以上这些就是延伸型的正规表示法的特殊字符。另外，要特别强调的是，那个! 在正规表示法当中
并不是特殊字符， 所以，如果你想要查出来文件中含有! 与> 的字行时，可以这样：
grep -n '[!>]' regular_express.txt
!

# 四，基础正规表示法练习
# 要了解正规表示法最简单的方法就是由实际练习去感受啦！所以在汇整正规表示法特殊符号前， 我们先以底下这个文件的内容来进行正规表示法的理解吧！先说明一下，底下的练习大前提是：
# 
# 语系已经使用『 export LANG=C 』的配置值；
# grep 已经使用 alias 配置成为『 grep --color=auto 』
# 以直接将底下的文字以 vi 储存成 regular_express.txt 这个文件， 不过，还是比较建议直接点底下的连结：
# http://cn.linux.vbird.org/linux_basic/0330regularex/regular_express.txt

# 例题一、搜寻特定字符串
#搜寻特定字符串很简单吧？假设我们要从刚刚的文件当中取得the 这个特定字符串，
#最简单的方式就是这样：
echo '(1)-----------------------完美分割线--------------------------------'
grep -n 'the' regular_express.txt
#那如果想要『反向选择』呢？也就是说，当该行没有'the' 这个字符串时才显示在屏幕上，那就直接
#使用：
echo '(2)-----------------------完美分割线--------------------------------'
grep -vn 'the' regular_express.txt
#你会发现，屏幕上出现的行列为除了8,12,15,16,18 五行之外的其他行列！ 接下来，如果你想要取得
#不论大小写的the 这个字符串，则：
echo '(3)-----------------------完美分割线--------------------------------'
grep -in 'the' regular_express.txt
#除了多两行(9, 14 行) 之外，第16 行也多了一个The 的关键词被撷取到喔！

#例题二、利用中括号[] 来搜寻集合字符
#如果我想要搜寻 test 或taste 这两个单字时，可以发现到，其实她们有共通的't?st' 存在～这个时候
#我可以这样来搜寻：
echo '(4)-----------------------完美分割线--------------------------------'
grep -n 't[ae]st' regular_express.txt
#了解了吧？其实[] 里面不论有几个字符，他都仅代表某『一个』字符， 所以，上面的例子说明了，
#我需要的字符串是『tast』或『test』两个字符串而已！ 而如果想要搜寻到有 oo 的字符时，则使用：
echo '(5)-----------------------完美分割线--------------------------------'
grep -n 'oo' regular_express.txt
#但是，如果我不想要oo 前面有g 的话呢？此时，可以利用在集合字符的反向选择[^] 来达成：
echo '(6)-----------------------完美分割线--------------------------------'
grep -n '[^g]oo' regular_express.txt
#意思就是说，我需要的是oo ，但是oo 前面不能是g 就是了！仔细比较上面两个表格，妳会发现，
#第1,9 行不见了，因为oo 前面出现了g 所致！第2,3 行没有疑问，因为foo 与Foo 均可被接受！
#但是第18 行明明有google 的goo 啊～别忘记了，因为该行后面出现了tool 的too 啊！所以该行
#也被列出来～ 也就是说， 18 行里面虽然出现了我们所不要的项目(goo) 但是由于有需要的项目
#(too) ， 因此，是符合字符串搜寻的喔！
#至于第 19 行，同样的，因为goooooogle 里面的oo 前面可能是o ，例如： go(ooo)oogle ，所以，
#这一行也是符合需求的！
#再来，假设我oo 前面不想要有小写字符，所以，我可以这样写[^abcd....z]oo ， 但是这样似乎不怎
#么方便，由于小写字符的ASCII 上编码的顺序是连续的， 因此，我们可以将之简化为底下这样：
echo '(7)-----------------------完美分割线--------------------------------'
grep -n '[^a-z]oo' regular_express.txt
#3:Football game is not use feet only.
#也就是说，当我们在一组集合字符中，如果该字符组是连续的，例如大写英文/小写英文/数字等等， 就
#可以使用[a-z],[A-Z],[0-9]等方式来书写，那么如果我们的要求字符串是数字与英文呢？ 呵呵！就将
#他全部写在一起，变成：[a-zA-Z0-9]。例如，我们要取得有数字的那一行，就这样：
echo '(8)-----------------------完美分割线--------------------------------'
grep -n '[0-9]' regular_express.txt
#但由于考虑到语系对于编码顺序的影响，因此除了连续编码使用减号『- 』之外， 你也可以使用如
#下的方法来取得前面两个测试的结果：
echo '(9)-----------------------完美分割线--------------------------------'
grep -n '[^[:lower:]]oo' regular_express.txt
## 那个 [:lower:] 代表的就是 a-z 的意思！请参考前两小节的说明表格
echo '(10)-----------------------完美分割线--------------------------------'
grep -n '[[:digit:]]' regular_express.txt
#啥？上头在写啥东西呢？不要害怕！分开来瞧一瞧。我们知道[:lower:] 就是a-z 的意思，那么[a-z]
#当然就是[[:lower:]] 啰！鸟哥第一次接触正规表示法的时候，看到两层中括号差点昏倒～完全看不
#懂！现在，请注意那个迭代的意义， 自然就能够比较清楚了解啰！
#这样对于 [] 以及[^] 以及[] 当中的- ，还有关于前面表格提到的特殊关键词有了解了吗？^_^！

# 例题三、行首与行尾字符^ $
#我们在例题一当中，可以查询到一行字符串里面有 the 的，那如果我想要让the 只在行首列出呢？
#这个时候就得要使用制表符了！我们可以这样做：
echo '(11)-----------------------完美分割线--------------------------------'
grep -n '^the' regular_express.txt
#此时，就只剩下第12 行，因为只有第12 行的行首是the 开头啊～此外， 如果我想要开头是小写
#字符的那一行就列出呢？可以这样：
echo '(12)-----------------------完美分割线--------------------------------'
grep -n '^[a-z]' regular_express.txt
#你可以发现我们可以捉到第一个字符都不是大写的！上面的指令也可以用如下的方式来取代的：
echo '(13)-----------------------完美分割线--------------------------------'
grep -n '^[[:lower:]]' regular_express.txt
#好！那如果我不想要开头是英文字母，则可以是这样：
echo '(14)-----------------------完美分割线--------------------------------'
grep -n '^[^a-zA-Z]' regular_express.txt
## 指令也可以是： grep -n '^[^[:alpha:]]' regular_express.txt
: <<!
 注意:到了吧？那个^ 符号，在字符集合符号(括号[])之内与之外是不同的!
 在 [] 内代表『反向选择』，
 在[] 之外则代表定位在行首的意义！
!

#反过来思考，那如果我想要找出来，行尾结束
#为小数点(.) 的那一行，该如何处理：
echo '(15)-----------------------完美分割线--------------------------------'
grep -n '\.$' regular_express.txt
#特别注意到，因为小数点具有其他意义(底下会介绍)，所以必须要使用跳脱字符(\)来加以解除其特殊
#意义！ 不过，你或许会觉得奇怪，但是第5~9 行最后面也是. 啊～怎么无法打印出来？ 这里就牵
#涉到Windows 平台的软件对于断行字符的判断问题了！我们使用cat -A 将第五行拿出来看， 你会
#发现：
echo '(16)-----------------------完美分割线--------------------------------'
cat -An regular_express.txt | head -n 10 | tail -n 6
#我们在第九章内谈到过断行字符在Linux 与Windows 上的差异， 在上面的表格中我们可以发现
#5~9 行为Windows 的断行字符(^M$) ，而正常的Linux 应该仅有第10 行显示的那样($) 。所以
#啰，那个. 自然就不是紧接在$ 之前喔！也就捉不到5~9 行了！这样可以了解^ 与$ 的意义吗？
#好了，先不要看底下的解答，自己想一想，那么如果我想要找出来，哪一行是『空白行』， 也就是
#说，该行并没有输入任何数据，该如何搜寻？
echo '(17)-----------------------完美分割线--------------------------------'
grep -n '^$' regular_express.txt
#因为只有行首跟行尾(^$)，所以，这样就可以找出空白行啦！再来，假设你已经知道在一个程序脚
#本(shell script) 或者是配置文件当中，空白行与开头为# 的那一行是批注，因此如果你要将资料列
#出给别人参考时， 可以将这些数据省略掉以节省保贵的纸张，那么你可以怎么作呢？ 我们以
#/etc/rsyslog.conf 这个文件来作范例，你可以自行参考一下输出的结果：
echo '(18)-----------------------完美分割线--------------------------------'
cat -n /etc/rsyslog.conf
## 在 CentOS 7 中，结果可以发现有 91 行的输出，很多空白行与 # 开头的批注行
echo '(19)-----------------------完美分割线--------------------------------'
grep -v '^$' /etc/rsyslog.conf | grep -v '^#'
## 结果仅有 14 行，其中第一个『 -v '^$' 』代表『不要空白行』，
## 第二个『 -v '^#' 』代表『不要开头是 # 的那行』喔！
#是否节省很多版面啊？另外，你可能也会问，那为何不要出现# 的符号的那行就直接舍弃呢？没办
#法！因为某些批注是与设定写在同一行的后面， 如果你只是抓 # 就予以去除，那就会将某些设定也
#同时移除了！那错误就大了～

# 例题四、任意一个字符. 与重复字符*
#在第十章bash 当中，我们知道通配符* 可以用来代表任意(0 或多个)字符， 但是正规表示法并不是
#通配符，两者之间是不相同的！ 至于正规表示法当中的『. 』则代表『绝对有一个任意字符』的意
#思！这两个符号在正规表示法的意义如下：
:<<!
. (小数点)：代表『一定有一个任意字符』的意思；
* (星星号)：代表『重复前一个字符， 0 到无穷多次』的意思，为组合形态
!
#这样讲不好懂，我们直接做个练习吧！假设我需要找出g??d 的字符串，亦即共有四个字符， 起头
#是g 而结束是d ，我可以这样做：
echo '(20)-----------------------完美分割线--------------------------------'
grep -n 'g..d' regular_express.txt
#因为强调g 与d 之间一定要存在两个字符，因此，第13 行的god 与第14 行的gd 就不会被列
#出来啦！再来，如果我想要列出有oo, ooo, oooo 等等的数据， 也就是说，至少要有两个(含) o 以上
#该如何是好？是o* 还是oo* 还是ooo* 呢？ 虽然你可以试看看结果， 不过结果太占版面了
#@_@ ，所以，我这里就直接说明。
#因为 * 代表的是『重复0 个或多个前面的RE 字符』的意义， 因此，『o*』代表的是：『拥有空
#字符或一个o 以上的字符』， 特别注意，因为允许空字符(就是有没有字符都可以的意思)，因此，
#『grep -n 'o*' regular_express.txt 』将会把所有的数据都打印出来屏幕上！
#那如果是『oo*』呢？则第一个o 肯定必须要存在，第二个o 则是可有可无的多个o ， 所以，凡
#是含有o, oo, ooo, oooo 等等，都可以被列出来～
#同理，当我们需要『至少两个o 以上的字符串』时，就需要ooo* ，亦即是：
echo '(21)-----------------------完美分割线--------------------------------'
grep -n 'ooo*' regular_express.txt
: '这样理解* 的意义了吗？好了，现在出个练习，如果我想要字符串开头与结尾都是g，但是两个g 之
#间仅能存在至少一个o ，亦即是gog, goog, gooog.... 等等，那该如何？'
echo '(22)-----------------------完美分割线--------------------------------'
grep -n 'goo*g' regular_express.txt
#如此了解了吗？再来一题，如果我想要找出g 开头与g 结尾的字符串，当中的字符可有可无，那该
#如何是好？是『g*g』吗？
echo '(23)-----------------------完美分割线--------------------------------'
grep -n 'g*g' regular_express.txt
#但测试的结果竟然出现这么多行？太诡异了吧？其实一点也不诡异，因为g*g 里面的g* 代表『空
#字符或一个以上的g』在加上后面的 g ，因此，整个RE 的内容就是g, gg, ggg, gggg ， 因此，
#只要该行当中拥有一个以上的g 就符合所需了！
#那该如何得到我们的 g....g 的需求呢？呵呵！就利用任意一个字符『.』啊！ 亦即是：『g.*g』的作
#法，因为* 可以是0 或多个重复前面的字符，而. 是任意字符，所以： 『.* 就代表零个或多个任
#意字符』的意思啦！
echo '(24)-----------------------完美分割线--------------------------------'
grep -n 'g.*g' regular_express.txt
#因为是代表g 开头与g 结尾，中间任意字符均可接受，所以，第1, 14, 20 行是可接受的喔！这个 .*
#的RE 表示任意字符是很常见的，希望大家能够理解并且熟悉！ 再出一题，如果我想要找出『任意
#数字』的行列呢？因为仅有数字，所以就成为：
echo '(25)-----------------------完美分割线--------------------------------'
grep -n '[0-9][0-9]*' regular_express.txt
#虽然使用grep -n '[0-9]' regular_express.txt 也可以得到相同的结果， 但鸟哥希望大家能够理解上面指
#令当中RE 表示法的意义才好！

# 例题五、限定连续RE 字符范围{}
#在上个例题当中，我们可以利用. 与RE 字符及* 来设定0 个到无限多个重复字符， 那如果我想
#要限制一个范围区间内的重复字符数呢？举例来说，我想要找出两个到五个o 的连续字符串，该如
#何作？这时候就得要使用到限定范围的字符{} 了。但因为{ 与} 的符号在shell 是有特殊意义
#的，因此， 我们必须要使用跳脱字符\ 来让他失去特殊意义才行。至于 {} 的语法是这样的，假设
#我要找到两个o 的字符串，可以是：
echo '(26)-----------------------完美分割线--------------------------------'
grep -n 'o\{2\}' regular_express.txt
#这样看似乎与ooo* 的字符没有什么差异啊？因为第19 行有多个o 依旧也出现了！ 好，那么换个
#搜寻的字符串，假设我们要找出g 后面接2 到5 个o ，然后再接一个g 的字符串，他会是这样：
echo '(27)-----------------------完美分割线--------------------------------'
grep -n 'go\{2,5\}g' regular_express.txt
#嗯！很好！第19 行终于没有被取用了(因为19 行有6 个o 啊！)。那么，如果我想要的是2 个
#o 以上的goooo....g 呢？除了可以是gooo*g ，也可以是：
echo '(28)-----------------------完美分割线--------------------------------'
grep -n 'go\{2,\}g' regular_express.txt
#呵呵！就可以找出来啦～

# 五，grep的一些进阶选项
echo '(29)-----------------------完美分割线--------------------------------'
dmesg | grep -n "sll"
echo '(30)-----------------------完美分割线--------------------------------'
dmesg | grep -n -a3 -b2 "sll"
